package utils

import (
	"bytes"
	"encoding/json"
	"image"
	"image/jpeg"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"golang.org/x/image/bmp"
)

type RoutesUtilsTestSuite struct {
	suite.Suite
	router           httprouter.Router
	server           httptest.Server
	filePath         string
	fieldName        string
	filePathInvalid  string
	fieldNameInvalid string
	testEndpoint     string
}

func (s *RoutesUtilsTestSuite) SetupSuite() {
	s.filePath = "test-file.jpg"
	s.fieldName = "image"
	s.filePathInvalid = "test-file.bmp"
	s.fieldNameInvalid = "image-invalid"
	s.testEndpoint = "/img-test"

	s.router = *httprouter.New()
	s.router.POST(s.testEndpoint, func(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
		req.ParseForm()
		imgBase64Str, _ := ProcessRequestImage(req)
		w.Header().Set("Content-Type", "application/json")
		resp, _ := json.Marshal(imgBase64Str)
		w.Write(resp)
	})
	s.server = *httptest.NewServer(&s.router)
}

func (s *RoutesUtilsTestSuite) createTestImage(validPath bool, validField bool) (*bytes.Buffer, string) {
	body := new(bytes.Buffer)
	mw := multipart.NewWriter(body)

	var fp, fn string
	if validPath {
		fp = s.filePath
	} else {
		fp = s.filePathInvalid
	}
	if validField {
		fn = s.fieldName
	} else {
		fn = s.fieldNameInvalid
	}

	myImg := image.NewRGBA(image.Rect(0, 0, 12, 6))
	out, _ := os.Create(fp)
	if validPath {
		jpeg.Encode(out, myImg, nil)
	} else {
		bmp.Encode(out, myImg)
	}
	out.Close()

	file, _ := os.Open(fp)
	w, _ := mw.CreateFormFile(fn, fp)
	io.Copy(w, file)

	mw.Close()
	return body, mw.FormDataContentType()
}

func (s *RoutesUtilsTestSuite) TestProcessTemplates() {
	templates := ProcessTemplates("../../../public/html/*.html")
	assert.NotNil(s.T(), templates)
}

func (s *RoutesUtilsTestSuite) TestSetSiteEnvs() {
	siteName, siteTitle := SetSiteEnvs()
	assert.Equal(s.T(), "Welcome to IMABON!", siteName)
	assert.Equal(s.T(), "IMABON", siteTitle)
}

func (s *RoutesUtilsTestSuite) TestProcessRequestImage() {
	body, contentType := s.createTestImage(true, true)

	resp, _ := http.Post(s.server.URL+s.testEndpoint, contentType, body)
	actual, _ := ioutil.ReadAll(resp.Body)
	resp.Body.Close()

	assert.Greater(s.T(), len(actual), 256)
}

func (s *RoutesUtilsTestSuite) TestProcessRequestImageEmpty() {
	req := http.Request{}

	base64Img, _ := ProcessRequestImage(&req)
	assert.Equal(s.T(), "", base64Img)
}

func (s *RoutesUtilsTestSuite) TestProcessRequestImageFormFail() {
	body, contentType := s.createTestImage(false, false)

	resp, _ := http.Post(s.server.URL+s.testEndpoint, contentType, body)
	actual, _ := ioutil.ReadAll(resp.Body)
	resp.Body.Close()

	assert.Less(s.T(), len(actual), 256)
}

func (s *RoutesUtilsTestSuite) TestProcessRequestImageTypeFail() {
	body, contentType := s.createTestImage(false, true)

	resp, _ := http.Post(s.server.URL+s.testEndpoint, contentType, body)
	actual, _ := ioutil.ReadAll(resp.Body)
	resp.Body.Close()

	assert.Less(s.T(), len(actual), 256)
}

func (s *RoutesUtilsTestSuite) TestIdToString() {
	id := primitive.NewObjectID()
	actual := IdToString(&id)
	expected := strings.TrimSuffix(strings.Split(id.String(), "(\"")[1], "\")")

	assert.Equal(s.T(), expected, actual)
}

func (s *RoutesUtilsTestSuite) TestSliceToString() {
	var slice = []string{"a", "b", "c"}
	stringSlice := SliceToString(slice)

	assert.Equal(s.T(), "abc", stringSlice)
}

func (s *RoutesUtilsTestSuite) TestDefaultToVal() {
	val := "test"
	actual := DefaultToVal(val)

	assert.Equal(s.T(), val, actual)
}

func (s *RoutesUtilsTestSuite) TestDefaultToValBlank() {
	val := ""
	actual := DefaultToVal(val)

	assert.Equal(s.T(), "Anon", actual)
}

func (s *RoutesUtilsTestSuite) TestDatetimeToString() {
	datetime := primitive.NewDateTimeFromTime(time.Now())
	expected := datetime.Time().Format("2006/01/02 (Mon) 15:04:05")
	actual := DatetimeToString(datetime)

	assert.Equal(s.T(), expected, actual)
}

func TestRoutesUtilsTestSuite(t *testing.T) {
	suite.Run(t, new(RoutesUtilsTestSuite))
}
